﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsxFactory.Modeling.Strategies;
using System.IO;
using VsxFactory.Modeling.Repository;
using System.Xml.Linq;
using VsxFactory.Modeling.VisualStudio;

namespace VsxFactory.Modeling.StrategyManager.Strategies
{
    class VisualStudioPackageRepository : ReferencePackageRepository
    {
        private IVsSolution _solution;
        private string _packageReferenceFile;

        public VisualStudioPackageRepository(IStrategyContext context, string packageKind)
            : base(context, packageKind)
        {
            System.Diagnostics.Contracts.Contract.Requires(context != null);

            _solution = context.SolutionExplorer.CurrentSolution;
            _packageReferenceFile = Path.Combine(_solution.Path, PackageReferenceFile);
        }

        public override IQueryable<PackageInfo> GetPackages()
        {
            return base.GetPackages();
        }

        public override PackageSource Source
        {
            get { return new PackageSource("Activated", Path.GetDirectoryName(_packageReferenceFile)); }
        }

        protected override System.Xml.Linq.XDocument GetDocument(bool createIfNotExists = false)
        {
            if (File.Exists(_packageReferenceFile))
            { 
                using (var reader = System.Xml.XmlReader.Create(_packageReferenceFile))
                {
                    return XDocument.Load(reader);
                }
            }

            // If it doesn't exist and we're creating a new file then return a
            // document with an empty packages node
            if (createIfNotExists)
            {
                return new XDocument(new XElement("packages"));
            }

            return null;
        }

        protected override void SaveDocument(System.Xml.Linq.XDocument document) {
            var node = _solution.SolutionItems.FindByPath<IVsProjectFileItem>(_packageReferenceFile);
            if (node != null) {
                using (StringWriter sw = new StringWriter()) {
                    document.Save(sw);
                    node.ReplaceText(sw.ToString(), Encoding.Unicode);
                }
            }
            else {
                Utils.UnsetReadOnly(_packageReferenceFile);
                document.Save(_packageReferenceFile);
                _solution.AddItem(_packageReferenceFile);
            }
        }

        public override void InstallPackage(VsxFactory.Modeling.Repository.PackageInfo package)
        {
            Context.WriteLogMessage("Activate strategy {0}", package.ID);
            base.InstallPackage(package);
        }

        private bool CheckVersion(Version localVersion, string version, bool includeEqual)
        {
            var v = Version.Parse(version); 
            return v.Major == localVersion.Major && 
                v.Minor == localVersion.Minor && 
                (v.Build > localVersion.Build ||
                    (v.Build == localVersion.Build && (v.Revision > localVersion.Revision || v.Revision == localVersion.Revision && includeEqual))
                );
        }

        /// <summary>
        /// Synchronize active packages
        /// </summary>
        public override void SynchronizeStrategies()
        {
            Context.WriteLogMessage("Synchronize strategies");
            SynchronizeStrategies(this.GetPackages());
        }

        /// <summary>
        /// Vérification pour chaque package local si il n'existe pas une version plus récente.
        /// </summary>
        /// <param name="localPackages"></param>
        private void SynchronizeStrategies(IQueryable<PackageInfo> localPackages)
        {
            IPackageRepository onlineRepository = null;

            // Parcours des stratégies qui sont actives dans la solution.
            foreach (var package in localPackages.OrderBy(p => p.RepositorySource))
            {
                if (package.Kind == PackageKind.System)
                    continue;

                // Si il n'y a pas de source, on prend le défaut
                var src = package.RepositorySource;
                if (String.IsNullOrWhiteSpace(package.RepositorySource) && Context.Settings.DefaultRepositorySource != null)
                    src = Context.Settings.DefaultRepositorySource.Source;

                // Le package a t il été copié ou vient il d'un serveur distant ?
                if (String.IsNullOrEmpty(src))
                {
                    onlineRepository = null;
                    src = "local disk";
                }
                // Si on est toujours sur la meme source, on conserve le repository precedent
                else if (onlineRepository == null || onlineRepository.Source.Source != src)
                    onlineRepository = new OnlinePackageRepository(Context, new PackageSource(String.Empty, src));

                try
                {
                    PackageInfo packageToExtract = null;
                    Version pv = Version.Parse(package.Version);

                    // Recherche si le package est présent sur le disque et qu'il correspond à la version voulue
                    var packageFileName = Context.Settings.ResolvePackageFileName(package);
                    if (File.Exists(packageFileName))
                    {
                        var pi = PackageHelper.GetManifestFromPackage(packageFileName);
                        if (pi != null && CheckVersion(pv, pi.Version, true))
                            packageToExtract = new PackageInfo(pi); // Oui il existe une version plus récente sur le disque
                    }
                    
                    // Sinon on essaye de le récupèrer à partir du referentiel
                    if( packageToExtract == null && onlineRepository != null)
                    {
                        var onlineStrategies = (from p in onlineRepository.GetPackages()
                                                where p.ID == package.ID
                                                select p).ToList();

                        // Les mises à jour automatiques ne se font que sur la révision et le build
                        packageToExtract = onlineStrategies.LastOrDefault(p => CheckVersion(pv, p.Version, false));
                    
                        if (packageToExtract == null)
                            Context.WriteLogMessage("Synchronize strategy {0}, version = {1} : OK", package.Name, package.Version);
                    }

                    if (packageToExtract != null)
                    {
                        Context.WriteLogMessage("Synchronize strategy {0}, version = {1} : Download version {2} from {3}", package.Name, package.Version, packageToExtract.Version, src);
                        ((VisualStudioStrategyContext)Context).LocalRepository.InstallPackage(packageToExtract);
                        base.InstallPackage(packageToExtract);
                    }
                }
                catch(Exception ex)
                {
                    Context.WriteError("Synchronization error with repository {0} - {1}", src, ex.Message);
                }
            }
        }
    }
}
